/*
 *  Copyright 2007 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.struts;

import org.apache.struts.tiles.xmlDefinition.XmlParser;
import org.apache.commons.digester.Digester;

import java.net.URL;

/**
 * <p>Parser which parses configuration based on slightly modified DTD. This
 * DTD <code>putList</code> elements to have boolean <b>container</b> attribute.
 * If such list is marked as a container, some definition names may be put
 * into this list in run-time. Definitions whose names may be put into a list
 * are marked with <b>containers</b> attribute, which constrains lists into
 * which such definition may be put.</p>
 * <p>Most of the code of this class is copied from XmlParser class.</p>
 * <p><a href="ContainerXmlParser.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.1 $ $Date: 2007/04/07 13:05:01 $
 */
public class ContainerXmlParser extends XmlParser {

    /**
     * <p>The set of public identifiers, and corresponding resource names for
     * the versions of the configuration file DTDs we know about.  There
     * <strong>MUST</strong> be an even number of Strings in this list!</p>
     * <p>Here, link to modified DTD is placed.</p>
     */
    protected String registrations[] = {
        "-//Blandware//DTD Tiles Configuration 1.1 with containers//EN",
        "/tiles-config_1_1_with_containers.dtd"
    };


    /**
     * Constructor.
     * Creates a digester parser and initializes syntax rules.
     */
    public ContainerXmlParser() {
        digester = new Digester();
        digester.setValidating(validating);
        digester.setNamespaceAware(true);
        digester.setUseContextClassLoader(true);
        // Register our local copy of the DTDs that we can find
        for (int i = 0; i < registrations.length; i += 2) {
            URL url = this.getClass().getResource(registrations[i + 1]);
            if (url != null) {
                digester.register(registrations[i], url.toString());
            }
        }
        // Init syntax rules
        initDigester(digester);
    }

    /**
     * Init digester.
     *
     * @param digester Digester instance to use.
     */
    protected void initDigester(Digester digester) {
        initDigesterForTilesDefinitionsSyntax( digester );
        initDigesterForComponentsDefinitionsSyntax( digester );
        initDigesterForInstancesSyntax( digester );
    }

    /**
     * Init digester for components syntax.
     * This is an old set of rules, left for backward compatibility.
     *
     * @param digester Digester instance to use.
     */
    protected void initDigesterForComponentsDefinitionsSyntax(Digester digester) {
        // Common constants
        String PACKAGE_NAME = "org.apache.struts.tiles.xmlDefinition";
        String DEFINITION_TAG = "component-definitions/definition";
        String definitionHandlerClass = ContainedXmlDefinition.class.getName();

        String PUT_TAG = DEFINITION_TAG + "/put";
        String putAttributeHandlerClass = PACKAGE_NAME + ".XmlAttribute";

        String LIST_TAG = DEFINITION_TAG + "/putList";
        String listHandlerClass = ContainerXmlListAttribute.class.getName();

        String ADD_LIST_ELE_TAG = LIST_TAG + "/add";

        // syntax rules
        digester.addObjectCreate(DEFINITION_TAG, definitionHandlerClass);
        digester.addSetProperties(DEFINITION_TAG);
        digester.addSetNext(DEFINITION_TAG, "putDefinition",
                           definitionHandlerClass);
        // put / putAttribute rules
        digester.addObjectCreate(PUT_TAG, putAttributeHandlerClass);
        digester.addSetNext(PUT_TAG, "addAttribute", putAttributeHandlerClass);
        digester.addSetProperties(PUT_TAG);
        digester.addCallMethod(PUT_TAG, "setBody", 0);
        // list rules
        digester.addObjectCreate(LIST_TAG, listHandlerClass);
        digester.addSetProperties(LIST_TAG);
        digester.addSetNext(LIST_TAG, "addAttribute", putAttributeHandlerClass);
        // list elements rules
        // We use Attribute class to avoid rewriting a new class.
        // Name part can't be used in listElement attribute.
        digester.addObjectCreate(ADD_LIST_ELE_TAG, putAttributeHandlerClass);
        digester.addSetNext(ADD_LIST_ELE_TAG, "add", putAttributeHandlerClass);
        digester.addSetProperties(ADD_LIST_ELE_TAG);
        digester.addCallMethod(ADD_LIST_ELE_TAG, "setBody", 0);
    }

    /**
     * Init digester for Tiles syntax.
     * Same as components, but with first element = tiles-definitions
     *
     * @param digester Digester instance to use.
     */
    protected void initDigesterForTilesDefinitionsSyntax(Digester digester) {
        // Common constants
        String PACKAGE_NAME = "org.apache.struts.tiles.xmlDefinition";
        String DEFINITION_TAG = "tiles-definitions/definition";
        String definitionHandlerClass = ContainedXmlDefinition.class.getName();

        String PUT_TAG = DEFINITION_TAG + "/put";
        String putAttributeHandlerClass = PACKAGE_NAME + ".XmlAttribute";

        //String LIST_TAG = DEFINITION_TAG + "/putList";
        // List tag value
        String LIST_TAG = "putList";
        String DEF_LIST_TAG = DEFINITION_TAG + "/" + LIST_TAG;
        String listHandlerClass = ContainerXmlListAttribute.class.getName();
        // Tag value for adding an element in a list
        String ADD_LIST_ELE_TAG = "*/" + LIST_TAG + "/add";

        // syntax rules
        digester.addObjectCreate(DEFINITION_TAG, definitionHandlerClass);
        digester.addSetProperties(DEFINITION_TAG);
        digester.addSetNext(DEFINITION_TAG, "putDefinition",
                           definitionHandlerClass);
        // put / putAttribute rules
        // Rules for a same pattern are called in order, but rule.end() are called
        // in reverse order.
        // SetNext and CallMethod use rule.end() method. So, placing SetNext in
        // first position ensure it will be called last (sic).
        digester.addObjectCreate(PUT_TAG, putAttributeHandlerClass);
        digester.addSetNext(PUT_TAG, "addAttribute", putAttributeHandlerClass);
        digester.addSetProperties(PUT_TAG);
        digester.addCallMethod(PUT_TAG, "setBody", 0);
        // Definition level list rules
        // This is rules for lists nested in a definition
        digester.addObjectCreate(DEF_LIST_TAG, listHandlerClass);
        digester.addSetProperties(DEF_LIST_TAG);
        digester.addSetNext(DEF_LIST_TAG, "addAttribute",
                           putAttributeHandlerClass);
        // list elements rules
        // We use Attribute class to avoid rewriting a new class.
        // Name part can't be used in listElement attribute.
        digester.addObjectCreate(ADD_LIST_ELE_TAG, putAttributeHandlerClass);
        digester.addSetNext(ADD_LIST_ELE_TAG, "add", putAttributeHandlerClass);
        digester.addSetProperties(ADD_LIST_ELE_TAG);
        digester.addCallMethod(ADD_LIST_ELE_TAG, "setBody", 0);

        // nested list elements rules
        // Create a list handler, and add it to parent list
        String NESTED_LIST = "*/" + LIST_TAG + "/" + LIST_TAG;
        digester.addObjectCreate(NESTED_LIST, listHandlerClass);
        digester.addSetProperties(NESTED_LIST);
        digester.addSetNext(NESTED_LIST, "add", putAttributeHandlerClass);

        // item elements rules
        // We use Attribute class to avoid rewriting a new class.
        // Name part can't be used in listElement attribute.
        //String ADD_WILDCARD = LIST_TAG + "/addItem";
        // non String ADD_WILDCARD = LIST_TAG + "/addx*";
        String ADD_WILDCARD = "*/item";
        String menuItemDefaultClass
               = "org.apache.struts.tiles.beans.SimpleMenuItem";
        digester.addObjectCreate(ADD_WILDCARD, menuItemDefaultClass,
                                "classtype");
        digester.addSetNext(ADD_WILDCARD, "add", "java.lang.Object");
        digester.addSetProperties(ADD_WILDCARD);

        // bean elements rules
        String BEAN_TAG = "*/bean";
        String beanDefaultClass = "org.apache.struts.tiles.beans.SimpleMenuItem";
        digester.addObjectCreate(BEAN_TAG, beanDefaultClass, "classtype");
        digester.addSetNext(BEAN_TAG, "add", "java.lang.Object");
        digester.addSetProperties(BEAN_TAG);

        // Set properties to surrounding element
        digester.addSetProperty(BEAN_TAG + "/set-property", "property", "value");
    }

    /**
     * Init digester in order to parse instances definition file syntax.
     * Instances is an old name for "definition". This method is left for
     * backwards compatibility.
     *
     * @param digester Digester instance to use.
     */
    protected void initDigesterForInstancesSyntax(Digester digester) {
        // Build a digester to process our configuration resource
        String PACKAGE_NAME = "org.apache.struts.tiles.xmlDefinition";
        String INSTANCE_TAG = "component-instances/instance";
        String instanceHandlerClass = ContainedXmlDefinition.class.getName();

        String PUT_TAG = INSTANCE_TAG + "/put";
        String PUTATTRIBUTE_TAG = INSTANCE_TAG + "/putAttribute";
        String putAttributeHandlerClass = PACKAGE_NAME + ".XmlAttribute";

        String LIST_TAG = INSTANCE_TAG + "/putList";
        String listHandlerClass = ContainerXmlListAttribute.class.getName();

        String ADD_LIST_ELE_TAG = LIST_TAG + "/add";

        // component instance rules
        digester.addObjectCreate(INSTANCE_TAG, instanceHandlerClass);
        digester.addSetProperties(INSTANCE_TAG);
        digester.addSetNext(INSTANCE_TAG, "putDefinition", instanceHandlerClass);
        // put / putAttribute rules
        digester.addObjectCreate(PUTATTRIBUTE_TAG, putAttributeHandlerClass);
        digester.addSetProperties(PUTATTRIBUTE_TAG);
        digester.addSetNext(PUTATTRIBUTE_TAG, "addAttribute",
                           putAttributeHandlerClass);
        // put / putAttribute rules
        digester.addObjectCreate(PUT_TAG, putAttributeHandlerClass);
        digester.addSetProperties(PUT_TAG);
        digester.addSetNext(PUT_TAG, "addAttribute", putAttributeHandlerClass);
        // list rules
        digester.addObjectCreate(LIST_TAG, listHandlerClass);
        digester.addSetProperties(LIST_TAG);
        digester.addSetNext(LIST_TAG, "addAttribute", putAttributeHandlerClass);
        // list elements rules
        // We use Attribute class to avoid rewriting a new class.
        // Name part can't be used in listElement attribute.
        digester.addObjectCreate(ADD_LIST_ELE_TAG, putAttributeHandlerClass);
        digester.addSetProperties(ADD_LIST_ELE_TAG);
        digester.addSetNext(ADD_LIST_ELE_TAG, "add", putAttributeHandlerClass);
    }
}
